﻿using System;
using Firefly.Framework.ContentManagement;
using Firefly.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Firefly.Framework.Components.Sky
{
    public class SkyDomeShader : ShaderProxy, IShaderProxy
    {
        public enum Technique
        {
            Render,
            Update
        }

        private Vector3 _wavelength;
        private QuadRenderer _quadRenderer;
        private string _starfieldAsset;

        public Texture2D MieTexture
        {
            get { return Effect.Parameters["MieTexture"].GetValueTexture2D(); }
            set { Effect.Parameters["MieTexture"].SetValue(value); }
        }
        public Texture2D RayleighTexture
        {
            get { return Effect.Parameters["RayleighTexture"].GetValueTexture2D(); }
            set { Effect.Parameters["RayleighTexture"].SetValue(value); }
        }
        public Texture2D StarfieldTexture
        {
            get { return Effect.Parameters["StarfieldTexture"].GetValueTexture2D(); }
            set { Effect.Parameters["StarfieldTexture"].SetValue(value); }
        }
        public Vector3 SunlightDirection
        {
            get { return Effect.Parameters["SunDirection"].GetValueVector3(); }
            set { Effect.Parameters["SunDirection"].SetValue(value); }
        }
        public Vector3 InverseWavelength
        {
            get { return Effect.Parameters["InverseWavelength"].GetValueVector3(); }
            set { Effect.Parameters["InverseWavelength"].SetValue(value); }
        }
        public Vector3 MieWavelength
        {
            get { return Effect.Parameters["MieWavelength"].GetValueVector3(); }
            set { Effect.Parameters["MieWavelength"].SetValue(value); }
        }
        public Vector3 Wavelength
        {
            get
            {
                return _wavelength;
            }
            set
            {
                _wavelength = value;
                SetWaveLengths();
            }
        }
        public int NumberOfSamples
        {
            get { return Effect.Parameters["NumberOfSamples"].GetValueInt32(); }
            set { Effect.Parameters["NumberOfSamples"].SetValue(value); }
        }
        public float Exposure
        {
            get { return Effect.Parameters["Exposure"].GetValueSingle(); }
            set { Effect.Parameters["Exposure"].SetValue(value); }
        }
        public float StarIntensity
        {
            get { return Effect.Parameters["StarIntensity"].GetValueSingle(); }
            set { Effect.Parameters["StarIntensity"].SetValue(value); }
        }
        public Technique CurrentTechnique { get; set; }

        public SkyDomeShader(ISkyDome parent, string starfieldAsset)
            : base(parent)
        {
            _starfieldAsset = starfieldAsset;
        }

        public void SetTechnique(Technique technique)
        {
            CurrentTechnique = technique;
            Effect.CurrentTechnique = Effect.Techniques[technique.ToString()];
        }

        private void SetWaveLengths()
        {
            Vector3 inverseWavelength;
            Vector3 mieWavelength;
            inverseWavelength.X = 1.0f / (float)Math.Pow((double)_wavelength.X, 4.0);
            inverseWavelength.Y = 1.0f / (float)Math.Pow((double)_wavelength.Y, 4.0);
            inverseWavelength.Z = 1.0f / (float)Math.Pow((double)_wavelength.Z, 4.0);
            mieWavelength.X = (float)Math.Pow((double)_wavelength.X, -0.84);
            mieWavelength.Y = (float)Math.Pow((double)_wavelength.Y, -0.84);
            mieWavelength.Z = (float)Math.Pow((double)_wavelength.Z, -0.84);
            InverseWavelength = inverseWavelength;
            MieWavelength = mieWavelength;
        }

        #region IShaderProxy Members

        public override Matrix View { get; set; }
        public override Matrix Projection { get; set; }
        public override Matrix World { get; set; }

        public override void Draw(GameTime gameTime)
        {
            Effect.Begin();

            switch (CurrentTechnique)
            {
                case Technique.Render:
                    SkyDome s = Parent as SkyDome;
                    Effect.Parameters["WorldViewProjection"].SetValue(World * (View * Projection));

                    foreach (EffectPass pass in Effect.CurrentTechnique.Passes)
                    {
                        pass.Begin();
                        s.Dome.DrawUserIndexPrimitives();
                        pass.End();
                    }
                    break;
                case Technique.Update:
                    EffectPass updatePass = Effect.CurrentTechnique.Passes[0];
                    updatePass.Begin();
                    _quadRenderer.Draw(gameTime);
                    updatePass.End();
                    break;
            }

            Effect.End();
        }

        #endregion

        #region IHasContent

        public override void LoadContent()
        {
            EffectName = "Effects/Skies/Scattering";
            Effect = ContentService.Instance.Load<Effect>(EffectName);
            Effect.CurrentTechnique = Effect.Techniques["Render"];
            SunlightDirection = new Vector3(100.0f, 100.0f, 100.0f);
            Wavelength = new Vector3(0.65f, 0.57f, 0.475f);
            Exposure = -2.0f;
            NumberOfSamples = 20;
            StarIntensity = 0.0f;
            
            if (string.IsNullOrEmpty(_starfieldAsset))
            {
                _starfieldAsset = "Textures\\Skies\\Starfield";
            }
                
            StarfieldTexture = ContentService.Instance.Load<Texture2D>(_starfieldAsset);
            _quadRenderer = new QuadRenderer((Parent as SkyDome).Game);
            _quadRenderer.LoadContent();
        }

        public override void UnloadContent()
        {
            ContentService.Instance.Release(EffectName);
            _quadRenderer.Dispose();
            ContentService.Instance.Release(_starfieldAsset);
        }

        #endregion

        #region IDisposable Members

        ~SkyDomeShader()
        {
            Dispose(false);
        }

        #endregion
    }
}
